CPP MOLE 2.0 grid implementation + error handling - #442
Tony-Drummond wants to merge 42 commits into
Conversation
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.1 to 12.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](python-pillow/Pillow@12.1.1...12.3.0) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.3.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Fix version specification for Pillow in requirements.txt Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
Added debugging information to CMake configuration step. Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
New directory structure with new implementations for MOLE grids, arrays, errors, and utilities. New README files in every subdirectory fixed errors
Added debugging information to CMake configuration step. Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
New directory structure with new implementations for MOLE grids, arrays, errors, and utilities. New README files in every subdirectory fixed errors grid basic struct makegrid added Including DEBUGING MODE Adding testing and build utilities CMakefiles are updated
…_0_grid_implementation
Parses variadic <attribute, value> pairs into a gridRaw, validates dimensionality, cell counts, topology and isPeriodic length at parse time, then narrows into gridParams1D/2D/3D and calls makeGrid. Failures return a gridNull carrying the error stack. isPeriodic takes a pointer to a vector<bool> so its size can be checked against dim.
29 cases covering parse-time validation through runChecks and dimension dispatch through the gridBuilder macro, including isPeriodic size mismatches and out-of-order attributes.
Four examples covering 1D construction, 2D with periodicity, argument order independence, and parse-time error reporting.
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.1 to 12.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](python-pillow/Pillow@12.1.1...12.3.0) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.3.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Fix version specification for Pillow in requirements.txt Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.1 to 12.3.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](python-pillow/Pillow@12.1.1...12.3.0) --- updated-dependencies: - dependency-name: pillow dependency-version: 12.3.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Fix version specification for Pillow in requirements.txt Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
Fix version specification for Pillow in requirements.txt Signed-off-by: Christopher Paolini <paolini@engineering.sdsu.edu>
Added the example/sys files
Adds gridBase::applyDebugMode and a debug_mode overload of each grid1D/2D/3D constructor. The mode is applied once at construction and is not stored. The trigger is isValidatedGrid() rather than hasGridErrors(): a grid carries MOLE_ERR_GRID_UNCHECKED until validation clears it, and mergeErrors folds upstream errors into the same stack, so a valid grid can hold errors it did not cause. Aborting on those would end user programs over grids that are fine. Reporting is non-destructive in every mode, so a caller can still print or write the log afterwards. Makes gridBase's constructor protected and gridNull's private, reachable only through makeGridNull. Both classes are internal. 15 new tests, all binaries green.
Parses a debug pair carrying a MOLE debug mode and applies it to the resulting grid. Dispatch moves into buildGrid so that every path, including both failure paths, funnels through one point. The debug pair has to come first: parsing stops at an unrecognized attribute name, so a trailing debug pair is never read on the calls that need it. Documented in the header and asserted in the tests.
Codes 14-17 mapped to empty strings; code 16 is logged by gridBuilder on an unknown attribute, so users saw a bare code with no message. Renumbers the dictionary comments (011-017, not 001-007) and corrects the gridBuilder range to 10-99.
This reverts commit c17bd8f.
This reverts commit 91ed514.
This fixes an issue with the semantics of validatedGrid(). With this fix, if a grid fails during validation it is not marked as validated but it's also flagged as having errors. Update CMakeLists.txt
1667e51 to
cf6fe07
Compare
This fixes an issue with the semantics of validatedGrid(). With this fix, if a grid fails during validation it is not marked as validated but it's also flagged as having errors.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate issues affect builds, linking, validation, safety, and API correctness.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Reorganizes the C++ implementation for MOLE 2.0, adding grids, arrays, error handling, utilities, examples, tests, and packaging infrastructure.
Changes:
- Adds 1D–3D grid, array, and grid-builder APIs.
- Introduces error tracking and numerical utilities.
- Adds CMake integration, documentation, examples, and regression tests.
File summaries
| File | Description |
|---|---|
cpp/tests/utils/test_utils.cpp |
Tests numerical utilities. |
cpp/tests/utils/README.md |
Documents utility tests. |
cpp/tests/support/mole_test.h |
Provides the test harness. |
cpp/tests/README.md |
Documents the test layout. |
cpp/tests/grids/test_makeGrid.cpp |
Tests grid factory dispatch. |
cpp/tests/grids/test_grid3D.cpp |
Tests 3D grids. |
cpp/tests/grids/test_grid2D.cpp |
Tests 2D grids. |
cpp/tests/grids/test_grid1D.cpp |
Tests 1D grids. |
cpp/tests/grids/test_grid_builder.cpp |
Tests grid-builder behavior. |
cpp/tests/grids/README.md |
Documents grid tests. |
cpp/tests/errors/test_errors.cpp |
Tests error-stack operations. |
cpp/tests/errors/README.md |
Documents error tests. |
cpp/tests/CMakeLists.txt |
Defines regression test targets. |
cpp/tests/arrays/test_arrays.cpp |
Tests array behavior. |
cpp/tests/arrays/README.md |
Documents array tests. |
cpp/src/utils/utils.cpp |
Implements numerical utilities. |
cpp/src/utils/README.md |
Documents utility sources. |
cpp/src/sys/README.md |
Documents system utilities. |
cpp/src/sys/MOLE_Errors.cpp |
Implements error logging. |
cpp/src/README.md |
Documents source organization. |
cpp/src/include/utils.h |
Declares utility APIs. |
cpp/src/include/README.md |
Documents public headers. |
cpp/src/include/MOLE_grids.h |
Declares grid APIs and structures. |
cpp/src/include/MOLE_errors.h |
Declares errors and debug modes. |
cpp/src/include/MOLE_arrays.h |
Declares array APIs. |
cpp/src/include/grid_builder.h |
Declares the grid builder. |
cpp/src/grids/README.md |
Documents grid sources. |
cpp/src/grids/MOLE_grids.cpp |
Implements grid construction and validation. |
cpp/src/grids/MOLE_arrays.cpp |
Implements array wrappers. |
cpp/src/grids/grid_builder.cpp |
Implements grid-builder parsing. |
cpp/src/CMakeLists.txt |
Defines and installs the library. |
cpp/README.md |
Documents the C++ project layout. |
cpp/examples/README.md |
Documents examples. |
cpp/examples/grids/README.md |
Lists grid examples. |
cpp/examples/grids/gridBuilder2D_basic.cpp |
Demonstrates 2D grid building. |
cpp/examples/grids/gridBuilder1D_basic.cpp |
Demonstrates 1D grid building. |
cpp/examples/grids/gridBuilder_error_handling.cpp |
Demonstrates builder failures. |
cpp/examples/grids/gridBuilder_arg_order.cpp |
Demonstrates argument ordering. |
cpp/examples/grids/grid2D_basic.cpp |
Demonstrates direct 2D construction. |
cpp/examples/grids/grid1D_basic.cpp |
Demonstrates direct 1D construction. |
cpp/examples/grids/err_grid2D_basic.cpp |
Demonstrates grid errors. |
cpp/examples/grids/CMakeLists.txt |
Builds grid examples. |
cpp/CMakeLists.txt |
Configures dependencies and subprojects. |
cpp/cmake/MOLEConfig.cmake.in |
Defines the package configuration template. |
CMakeLists.txt |
Integrates the new C++ subtree. |
.gitignore |
Ignores C++ build output. |
Review details
Suppressed comments (12)
cpp/src/grids/MOLE_arrays.cpp:331
- This reports value-equal arrays as unequal merely because their storage addresses differ, contradicting
operator==. Implement inequality asreturn !(*this == other);and update the public header's identity-based description.
bool array2D::operator!=(const array2D& other) const {
bool are_not_equal = false;
if (data_.n_rows != other.data_.n_rows ||
data_.n_cols != other.data_.n_cols) { // compare sizes
are_not_equal = true;
}
else if (data_.memptr() != other.data_.memptr()) { // same ptr?
are_not_equal = true;
}
return are_not_equal;
cpp/src/grids/MOLE_arrays.cpp:374
- This reports value-equal arrays as unequal merely because their storage addresses differ, contradicting
operator==. Implement inequality asreturn !(*this == other);and update the public header's identity-based description.
bool array3D::operator!=(const array3D& other) const {
bool are_not_equal = false;
if (data_.n_rows != other.data_.n_rows ||
data_.n_cols != other.data_.n_cols ||
data_.n_slices != other.data_.n_slices) { // compare sizes
are_not_equal = true;
}
else if (data_.memptr() != other.data_.memptr()) { // same ptr?
are_not_equal = true;
}
cpp/src/grids/MOLE_grids.cpp:655
- The curvilinear branch—and the adjacent nonuniform branch—checks only that both arrays are nonempty. A pair of 1×1 arrays with
m = n = 3is therefore marked validated while centers and faces remain empty, which can break downstream grid operations. Validate dimensional consistency and initialize all required layers before marking these grids valid.
case 'c': // User must provide at least nodal coordinates
if (grid.nodes_X.data_.is_empty() ||
grid.nodes_Y.data_.is_empty()){
cpp/src/grids/MOLE_grids.cpp:947
- The curvilinear branch—and the nonuniform branch below it—accepts any three nonempty arrays without checking their
(m+1,n+1,o+1)compatibility, then marks the grid validated with empty centers/faces. Validate the supplied geometry and construct or require every layer needed by downstream operators.
case 'c': // User must provide at least nodal coordinates
if (grid.nodes_X.data_.is_empty() || grid.nodes_Y.data_.is_empty() ||
grid.nodes_Z.data_.is_empty()){
cpp/src/grids/MOLE_grids.cpp:663
- The nonuniform path also accepts any two non-empty arrays, including mismatched shapes unrelated to the declared cell counts, and leaves center/face layers unbuilt. Such a grid is not safe for downstream operators despite
isValidatedGrid()returning true.
case 'n': // nonuniform grids require user-supplied nodes_X
if (grid.nodes_X.data_.is_empty() ||
grid.nodes_Y.data_.is_empty()) {
cpp/src/grids/MOLE_grids.cpp:957
- Any three non-empty arrays make this nonuniform 3D grid valid, even when their shapes disagree with each other and the declared cell counts; center and face arrays also remain empty. Validate the topology's complete coordinate contract before reporting success.
case 'n': // nonuniform grids require user-supplied nodes_X
if (grid.nodes_X.data_.is_empty() || grid.nodes_Y.data_.is_empty() ||
grid.nodes_Z.data_.is_empty()) {
cpp/src/grids/MOLE_grids.cpp:93
- Popping the source from newest to oldest and immediately pushing into the destination reverses the error history. Since printing also walks top-down, propagated errors appear in the opposite backtrace order; reverse through an intermediate stack before appending.
stack<MOLE_Errors> tmp_stk = inerrs;
while (!tmp_stk.empty()) {
logGridErr(tmp_stk.top().errCode, tmp_stk.top().errLocation,
tmp_stk.top().paramError);
tmp_stk.pop();
cpp/src/include/MOLE_errors.h:136
- This diagnostic directs users to
validateGrid(), but no such API exists; the implemented validation method isvalidGrid(). Reporting a nonexistent recovery action makes the new error handling misleading.
{MOLE_ERR_GRID_UNCHECKED,
"Grid has not been validated, call validateGrid() first"},
cpp/src/include/MOLE_grids.h:66
- Default construction leaves
topologyindeterminate. Constructing a grid without assigning this required field then reads an indeterminate value invalidGrid()instead of reliably logging an invalid topology.
char topology; // 'u'=uniform, 'c'=curvilinear, 'n'=non-uniform
cpp/src/include/MOLE_grids.h:89
- Default construction leaves
topologyindeterminate. Constructing a grid without assigning this required field then reads an indeterminate value invalidGrid()instead of reliably logging an invalid topology.
char topology; // 'u'=uniform, 'c'=curvilinear, 'n'=non-uniform
cpp/src/include/README.md:40
- The listed header name is misspelled; the installed/public file is
utils.h.
+ **utilis.h**: header file for all MOLE utility functions, which
cpp/src/sys/README.md:26
- The documented source filename is misspelled and does not match
MOLE_Errors.cpp, so readers cannot locate the implementation.
+ **MOLE_Erros.cpp**: a C++ implementation of MOLE's error handling
- Files reviewed: 45/46 changed files
- Comments generated: 29
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| ##add_subdirectory(tests/cpp) | ||
| #add_subdirectory(tests/matlab_octave) | ||
| #add_subdirectory(examples/cpp) | ||
| add_subdirectory(cpp) |
There was a problem hiding this comment.
This is also the cause of the GitHub action build errors. Change line 159 in CMakeLists.txt to
add_custom_target(cpp_all_build DEPENDS mole_C++ tests_C++ examples_C++ tests_matlab_octave)To separate the cpp build from the total build. Then the actions will pass.
| file(DOWNLOAD ${SUPERLU_TARBALL_URL} ${BUILD_DIR}/superlu-${SUPERLU_VERSION}.tar.gz) | ||
| execute_process(COMMAND ${CMAKE_COMMAND} -E tar xzf superlu-${SUPERLU_VERSION}.tar.gz WORKING_DIRECTORY ${BUILD_DIR}) |
There was a problem hiding this comment.
If implemented, check to make sure this doesn't break the build sequence.
| add_subdirectory(examples/grids) | ||
|
|
||
| # Custom target to build everything | ||
| add_custom_target(all_build DEPENDS mole_C++ tests_C++ examples_C++ tests_matlab_octave) |
There was a problem hiding this comment.
Ohh, see previous comments
| # target_link_libraries(mole PUBLIC ${ARMADILLO_LIBRARIES}) | ||
| # target_include_directories(mole PUBLIC ${ARMADILLO_INCLUDE_DIRS}) |
| void read_ErrorLog(int ErrorCode, std::string &location, | ||
| std::string &arrayName); |
| string filename = logType + getDateTimeString(); | ||
| std::ofstream outFile(filename); |
| void Utils::read_ErrorLog(int ErrorCode, std::string &location, | ||
| std::string &arrayName) { | ||
| if (!errs.empty()) { | ||
| MOLE_Errors topError = errs.top(); | ||
| ErrorCode = topError.errCode; | ||
| location = topError.errLocation; |
| mat sheet(m, n, fill::zeros); | ||
|
|
||
| // Build X | ||
| vec t(n, fill::ones); | ||
|
|
||
| X.zeros(m, n, o); | ||
| Y.zeros(m, n, o); | ||
| Z.zeros(m, n, o); |
| target_include_directories(${_mole_test_bin} PRIVATE support) | ||
| target_compile_options(${_mole_test_bin} PRIVATE -Wall -Wextra) | ||
|
|
||
| add_test(NAME ${_mole_test_name} COMMAND ${_mole_test_bin}) |
jbrzensk
left a comment
There was a problem hiding this comment.
There are a handful of CoPilot comments, some of mine, and a Claude explanation of the int vs reference issue. Basically the one "have-to" fix is the CMakeLists.txt to change the name so the GitHub actions pass.
Maybe the other I do not understand is the error-codes stacked with the vectors, but only treated as vectors later. I think those may need a validation check to make sure math works.
| template <typename ArrayT> | ||
| void drainArrayErrors(ArrayT& a) { | ||
| while (a.hasArrayErrors()) { | ||
| int errCode; |
There was a problem hiding this comment.
should this be initialized to something?
|
|
||
| if (grid.m <= 0 || grid.n <= 0) { | ||
| logGridErr(MOLE_ERR_INVALID_GRID_SIZE, "grid2D[construct]", | ||
| to_string(grid.m)); |
There was a problem hiding this comment.
If grid.n is wrong, reports grid.m
| string logType){ | ||
| // create a string for the filename | ||
| string filename = logType + getDateTimeString(); | ||
| std::ofstream outFile(filename); |
There was a problem hiding this comment.
Should check if file is open before writing.
| // carries the array's own error stack along with its data. A null | ||
| // pointer means the attribute was not supplied, so dst keeps its | ||
| // default and MOLE reads it as unset. | ||
| template <typename T> |
There was a problem hiding this comment.
So the array1D is a stacked object with the error stack and the vector in one shot. Later, you assume the error stack is empty, and start copying and pasting this around.
There should be some check, somewhere, that the error stack is empty and the size of this is the size of a std::vector.
| // Armadillo vecs and mats. It losgs and error when the two input | ||
| // vectors are not the same size. | ||
| // | ||
| double Utils::trapz(const vec &x, const vec &y) { |
There was a problem hiding this comment.
This needs a validation check the x and y are not 0, or something wierd.
| if (grid.m <= 0 || grid.n <= 0) { | ||
| logGridErr(MOLE_ERR_INVALID_GRID_SIZE, "grid2D[construct]", | ||
| to_string(grid.m)); | ||
| isValid = false; |
There was a problem hiding this comment.
And shouldn't the function break here if "isValid=False"?. Why keep building?
| bool isValid = true; | ||
|
|
||
| if (grid.m <= 0 || grid.n <= 0 || grid.o <=0) { | ||
| logGridErr(MOLE_ERR_INVALID_GRID_SIZE, "grid3D[construct]", |
There was a problem hiding this comment.
Same error as 2D, and should break out of function if grid is not valid.
| // user facing class like grids or operators. Users can also print the | ||
| // errors to standard output or write them to a file. | ||
| // | ||
| void array1D::read_ErrorLog(int ErrorCode, std::string &location, |
There was a problem hiding this comment.
Should be int &ErrorCode, but it still works?
I ran it though claude and this was it's response:
Confirmed — MOLEerr_print at [MOLE_Errors.cpp:158-162] does exactly that fallback: if err.errCode isn't a key in MOLE_errors_messages, it prints "Invalid MOLE Error code []". Now the full chain, precisely:
The by-value vs by-reference mismatch:
read_ErrorLog's signature ([MOLE_arrays.cpp:194]):
void array1D::read_ErrorLog(int ErrorCode, std::string &location, std::string &arrayName)
Only location and arrayName are references (&). ErrorCode is a plain int — a copy. Inside the function ([MOLE_arrays.cpp:198].
ErrorCode = topError.errCode; // writes to the LOCAL copy
location = topError.errLocation; // writes through the reference — caller sees this
arrayName = topError.paramError; // writes through the reference — caller sees this
ErrorCode = topError.errCode; mutates the function's own stack-local parameter. When the function returns, that copy is discarded — nothing propagates back to the caller. location and arrayName do propagate, because they're bound to the caller's actual variables via reference. So the function silently does 2 of its 3 "outputs" correctly and drops the third — which is easy to miss in review because the code reads as if all three behave the same way.
The caller, and exactly what your selection shows:
drainArrayErrors<ArrayT> ([MOLE_grids.h:162-169]):
template <typename ArrayT> void drainArrayErrors(ArrayT &a) {
while (a.hasArrayErrors()) {
int errCode; // <- line 164, uninitialized
string location, msgparam;
a.read_ErrorLog(errCode, location, msgparam);
logGridErr(errCode, location, msgparam);
}
}int errCode; at line 164 (the line you have selected) is declared with no initializer — its value is indeterminate stack garbage until something writes to it. The call to read_ErrorLog looks like it should fill it in (that's clearly the intent, matching location/msgparam), but because the parameter is by-value, it never does. So by the time logGridErr(errCode, ...) runs, errCode still holds whatever bytes happened to be sitting in that stack slot — reading it at all is undefined behavior in the strict sense, and in practice it's just whatever leftover value was there.
Downstream effect: logGridErr(size_t errCode, ...) ([MOLE_grids.h:141]) takes size_t, so the garbage int gets converted (sign-extended/wrapped if negative) into some arbitrary size_t and pushed onto the grid's real, user-visible error stack — this is the stack that print_ErrorLog()/write_ErrorLog() actually report from. The location and paramError strings attached to that entry are correct (e.g. "nd2DGrid" and a real descriptive message), but the errCode field is nonsense.
Concrete trigger path: grid2D::validGrid() → buildOrCheck2DCoords → nd2DGrid logs a real error like MOLE_ERR_INVALID_ARRAY_SIZE onto the array's own private a_errs stack when, e.g., mismatched coordinate array sizes are detected. buildOrCheck2DCoords then calls drainArrayErrors(X) to move that error onto the grid's stack — and that's the exact call that goes through the broken read_ErrorLog. So the grid ends up "flagged invalid" correctly (that part uses a separate mechanism, setCheckedWithErrors/MOLE_ERR_GRID_FLAGGED_W_ERRS), but if a user calls print_ErrorLog() to find out why, instead of seeing MOLE_ERR_INVALID_ARRAY_SIZE with its real message, they see "Invalid MOLE Error code [garbage]" — the diagnostic that was supposed to explain the failure is destroyed in transit.
The fix is mechanical: change all three read_ErrorLog declarations (and Utils::read_ErrorLog, same bug at [utils.h:125]/[utils.cpp:84]) from int ErrorCode to int &ErrorCode, matching the reference pattern already used for location/arrayName right next to it.
|
I'm closing this PR after re-organizing the mole directory structure with only MOLE 2.0 source code. For now, MOLE v1.2 files are being relocated (with 'git mv') in a temporary subdirectory and these files will be either refactored or replaced by the actual files as the MOLE 2.0 implementation progresses. |
What type of PR is this? (check all applicable)
Description
Objective
Bring the C++ implementation into compliance with the MOLE 2.0
API, numerical behavior, testing, documentation, and packaging standards.
mole/
├── cpp/
| │── cmake/
| │── doc/
| │── examples
| |── src/
| │ ├── boundaries
| │ └── grids
| │ └── include
| │ └── operators
| │ └── sys
| │ └── utils
| |── tests
Figure 1. C++ MOLE 2.0 implementation. Bolded subdirectories names are the ones impacted by this PR and related Git issues.
This PR implements first steps of CPP MOLE 2.0 implementation:
Related Issues & Documents
Issues, #437 (cpp directory reorganization) , #438 (error handling), #440 (cpp grid implementation)
QA Instructions, Screenshots, Recordings
Tests and examples for creating grid are included. There are several tests of the full CPP functionality included here.
To build:
Keep-open request
keep-open.Reason:
Added/updated tests?
_We encourage you to test all code included with MOLE, including examples.
have not been included
Read Contributing Guide and Code of Conduct
[optional] Are there any post deployment tasks we need to perform?
[optional] What gif best describes this PR or how it makes you feel?